#include <windows.h>
#include <iostream>
#include <time.h>

#include <EB0/EB0.h>
using namespace EB0;

namespace EB0
{
#pragma region Game Launcher
	// Run the game from start to finish including the loop
	void runGame()
	{
		// Initialize
		try
		{
			initialize();
		}
		catch (Debug::Exception& exc)
		{
			Debug::ErrorLog(exc.what());
		}

		// Main Loop
		try
		{
			mainLoop();
		}
		catch (Debug::Exception& exc)
		{
			Debug::ErrorLog(exc.what());
		}

		// Shutdown
		try
		{
			shutdown();
		}
		catch (Debug::Exception& exc)
		{
			Debug::ErrorLog(exc.what());
		}
	}
#pragma endregion

#pragma region Setup / Config / Initialization
	// Initialize
	void initialize()
	{
		// Managers will be created when they are first needed, as getSingletonPtr() handles creation
		// We need only worry about destroying the managers when the game ends

		// Create window
		System::gWindow = new Window::RenderWindow(800, 600, "Earthbound Zero Platformer");

		// Append default window listener which will handle closing of the window (Esc or X'ing the window)
		System::gDefaultWindowListener = new Window::DefaultWindowListener();
		System::gWindow->attachWindowListener(System::gDefaultWindowListener);

		// Configuration
		System::gTickLength = 10; // 100 ticks per second, 10 ms each
		System::gTickMaxLoops = 4; // rounding down of (1000 ms / 10 ticks) / 24 fps

		// Create states
		States::gTitleScreen = new States::TitleScreen;
		States::gThePlace = new States::ThePlace;
		States::gPause = new States::Pause;

		// Set initial state
		//pushState(States::gTitleScreen);
		pushState(States::gThePlace);

		// Set running flag
		System::gIsRunning = true;
	}
#pragma endregion

#pragma region Main Loop
	// Main Loop
	void mainLoop()
	{
		// Loop related variables
		clock_t tickCurrent = getClock();
		clock_t tickNext = tickCurrent;
		int tickNumLoops = 0;
		float dt = 0.0f; // interpolation for rendering

		clock_t windowEventLength;

		// Repeat until the game terminates
		while (isRunning())
		{
			// Handle window events, e.g. input
			windowEventLength = getClock();
			handleWindowEvents();
			windowEventLength = getClock() - windowEventLength;

			// Adjust tick loop and clock
			tickCurrent = getClock();
			tickNumLoops = 0;

			// Run game ticks, but only if the game is still currently running
			// We also have a limit on the maximum number of ticks we'll run before needed to render
			while (isRunning() && tickCurrent >= tickNext && tickNumLoops < System::gTickMaxLoops)
			{
				// Set next tick time
				tickNext += System::gTickLength;

				// Call game tick
				runTick();

				// Get current time and increment loop counter
				tickCurrent = getClock();
				tickNumLoops++;
			}

			// Draw window with interpolation
			if (isRunning())
			{
				dt = (float)(tickCurrent + System::gTickLength - tickNext) / (float)(1000.0f * System::gTickLength);
				renderWindow(dt);
			}

			// Exit on window close
			if (!System::gWindow || !System::gWindow->IsOpened()) // quit if no window or window not open
				quit();

			// Let's sleep if the game is processing way too fast
			// There's no use to keep running the while loop if nothing is happening.
			// This simple fix reduced my resource usage from 95% down to about 45%. Much needed!
			// I also reduce the sleep to take into account the duration of handleWindowEvents()
			if (isRunning() && tickNext > getClock() + windowEventLength)
				Sleep(tickNext - getClock() - windowEventLength);
		}
	}

	// Handle Window Events (our main source of input)
	void handleWindowEvents()
	{
		if (System::gWindow)
			System::gWindow->handleEvents();
	}

	// Run Game Tick
	void runTick()
	{
		// We'll use the same tick duration between every update
		// It's good to have a predictable amount of time, which
		// is why we made game ticks to begin with.
		static float dt = (float)System::gTickLength / 1000.0f; // 1.0f / (1000.0f / (float)System::gTickLength);

		// Update Window States
		if (System::gWindow)
			System::gWindow->update(dt);

		// Update Tasks (via task manager)
		Managers::TaskManager::getSingletonPtr()->update(dt);
	}

	// Draw the window
	void renderWindow(float dt)
	{
		if (System::gWindow)
			System::gWindow->render(dt);
	}
#pragma endregion

#pragma region Shutdown
	// Shutdown
	void shutdown()
	{
		// Destroy default window listener
		if (System::gDefaultWindowListener)
		{
			if (System::gWindow)
				System::gWindow->removeWindowListener(System::gDefaultWindowListener);

			delete System::gDefaultWindowListener;
			System::gDefaultWindowListener = 0;
		}

		// Destroy window, which destroys its StateManager and unloads all states
		if (System::gWindow)
		{
			delete System::gWindow;
			System::gWindow = 0;
		}

		// Destroy all states in memory
		if (States::gTitleScreen)
		{
			delete States::gTitleScreen;
			States::gTitleScreen = 0;
		}

		if (States::gThePlace)
		{
			delete States::gThePlace;
			States::gThePlace = 0;
		}

		if (States::gPause)
		{
			delete States::gPause;
			States::gPause = 0;
		}

		// Destroy Managers
		delete Managers::TaskManager::getSingletonPtr();
		delete Managers::AudioManager::getSingletonPtr();
		delete Managers::ImageManager::getSingletonPtr();

		// Reset variables
		System::gIsRunning = false;
		System::gTickLength = 0;
		System::gTickMaxLoops = 0;
	}
#pragma endregion

#pragma region Get and Set Values
	// Is the game still running?
	bool isRunning()
	{
		return System::gIsRunning;
	}

	// Tell the game to shutdown
	void quit()
	{
		System::gIsRunning = false;
	}
#pragma endregion

#pragma region Clock
	// Get the number of milliseconds since application start. Slightly optimized to avoid division.
	clock_t getClock()
	{
		if (CLOCKS_PER_SEC != 1000)
			return 1000 * clock() / CLOCKS_PER_SEC;
		return clock();
	}
#pragma endregion

#pragma region State Change Aliases
	// Push state alias
	void pushState(States::GameState* state)
	{
		if (System::gWindow && System::gWindow->getStateManager())
			System::gWindow->getStateManager()->pushState(state);
	}

	// Pop state alias
	void popState()
	{
		if (System::gWindow && System::gWindow->getStateManager())
			System::gWindow->getStateManager()->popState();
	}

	// Pop all states alias
	void popAllStates()
	{
		if (System::gWindow && System::gWindow->getStateManager())
			System::gWindow->getStateManager()->popAllStates();
	}

	// Change state alias
	void changeState(States::GameState* state)
	{
		if (System::gWindow && System::gWindow->getStateManager())
			System::gWindow->getStateManager()->changeState(state);
	}
#pragma endregion

#pragma region Task Aliases
	// Add Task
	void addTask(Tasks::GameTask* task)
	{
		Managers::TaskManager::getSingletonPtr()->addTask(task);
	}

	// Remove Task
	void removeTask(Tasks::GameTask* task)
	{
		Managers::TaskManager::getSingletonPtr()->removeTask(task);
	}

	// Remove All Tasks
	void removeAllTasks()
	{
		Managers::TaskManager::getSingletonPtr()->removeAllTasks();
	}
#pragma endregion
}